package com.ohuang.util_h;

import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.Handler;
import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;

import com.ohuang.hidenapibypass.HiddenApiBypass;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class AccessibilityNodeInfoUtil {

    public static final String TAG = "AccessibilityNodeInfoUtil";


    public static List<AccessibilityNodeInfo> getViewAccessibilityNodeInfo(View view) {
        List<AccessibilityNodeInfo> data = new ArrayList<>();
        if (view.getAccessibilityNodeProvider() != null) {
            AccessibilityNodeProvider accessibilityNodeProvider = view.getAccessibilityNodeProvider();
            getAccessibilityNodeInfo(data, accessibilityNodeProvider.createAccessibilityNodeInfo(-1), accessibilityNodeProvider, 100);
        } else {
            data.add(view.createAccessibilityNodeInfo());
            getAccessibilityNodeInfoForView(data, view);
        }
        return data;
    }

    private static void getAccessibilityNodeInfoForView(List<AccessibilityNodeInfo> outInfos, View root) {
        LinkedHashMap<View, AccessibilityNodeInfo> addedChildren =
                new LinkedHashMap<View, AccessibilityNodeInfo>();
        ArrayList<View> children = new ArrayList<>();
        root.addChildrenForAccessibility(children);
        final int childCount = children.size();
        for (int i = 0; i < childCount; i++) {
            View child = children.get(i);
            if (isShown(child)) {
                AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
                if (provider == null) {
                    AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
                    if (info != null) {
                        outInfos.add(info);
                        addedChildren.put(child, null);
                    }
                } else {
                    AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
                            AccessibilityNodeProvider.HOST_VIEW_ID);
                    if (info != null) {
                        outInfos.add(info);
                        addedChildren.put(child, info);
                    }
                }
            }
        }

        for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
            View addedChild = entry.getKey();
            AccessibilityNodeInfo virtualRoot = entry.getValue();
            if (virtualRoot == null) {
                getAccessibilityNodeInfoForView(outInfos, entry.getKey());
            } else {
                AccessibilityNodeProvider provider =
                        addedChild.getAccessibilityNodeProvider();
                getChildAccessibilityNodeInfo(outInfos, virtualRoot, provider, 100);
            }
        }
    }

    private static boolean isShown(View view) {
        return (view != null) && (view.getWindowVisibility() == View.VISIBLE && view.isShown());
    }

    private static void getChildAccessibilityNodeInfo(List<AccessibilityNodeInfo> data, AccessibilityNodeInfo accessibilityNodeInfo, AccessibilityNodeProvider accessibilityNodeProvider, int deep) {
        if (accessibilityNodeInfo != null && deep > 0) {
            if (accessibilityNodeInfo.getChildCount() > 0) {
                for (int i = 0; i < accessibilityNodeInfo.getChildCount(); i++) {
                    Object mChildNodeIds = RefInvoke.getFieldOjbect("android.view.accessibility.AccessibilityNodeInfo", accessibilityNodeInfo, "mChildNodeIds");
                    if (mChildNodeIds != null) {
                        long VIRTUAL_DESCENDANT_ID_MASK = 0xffffffff00000000L;
                        int VIRTUAL_DESCENDANT_ID_SHIFT = 32;
                        long get = (long) RefInvoke.invokeMethod("android.util.LongArray", "get", mChildNodeIds, new Class[]{int.class}, new Object[]{i});
                        int getVirtualDescendantId = (int) ((get & VIRTUAL_DESCENDANT_ID_MASK)
                                >> VIRTUAL_DESCENDANT_ID_SHIFT);
                        AccessibilityNodeInfo accessibilityNodeInfo1 = accessibilityNodeProvider.createAccessibilityNodeInfo(getVirtualDescendantId);
                        data.add(accessibilityNodeInfo1);
                        getChildAccessibilityNodeInfo(data, accessibilityNodeInfo1, accessibilityNodeProvider, deep - 1);
                    }
                }
            }
        }
    }

    private static void getAccessibilityNodeInfo(List<AccessibilityNodeInfo> data, AccessibilityNodeInfo accessibilityNodeInfo, AccessibilityNodeProvider accessibilityNodeProvider, int deep) {
        if (accessibilityNodeInfo != null && deep > 0) {
            data.add(accessibilityNodeInfo);
            if (accessibilityNodeInfo.getChildCount() > 0) {
                for (int i = 0; i < accessibilityNodeInfo.getChildCount(); i++) {
                    Object mChildNodeIds = RefInvoke.getFieldOjbect("android.view.accessibility.AccessibilityNodeInfo", accessibilityNodeInfo, "mChildNodeIds");
                    if (mChildNodeIds != null) {
                        long VIRTUAL_DESCENDANT_ID_MASK = 0xffffffff00000000L;
                        int VIRTUAL_DESCENDANT_ID_SHIFT = 32;
                        long get = (long) RefInvoke.invokeMethod("android.util.LongArray", "get", mChildNodeIds, new Class[]{int.class}, new Object[]{i});
                        int getVirtualDescendantId = (int) ((get & VIRTUAL_DESCENDANT_ID_MASK)
                                >> VIRTUAL_DESCENDANT_ID_SHIFT);
                        AccessibilityNodeInfo accessibilityNodeInfo1 = accessibilityNodeProvider.createAccessibilityNodeInfo(getVirtualDescendantId);
                        getAccessibilityNodeInfo(data, accessibilityNodeInfo1, accessibilityNodeProvider, deep - 1);
                    }
                }
            }
        }
    }

    public static List<AccessibilityNodeInfo> contains(List<AccessibilityNodeInfo> accessibilityNodeInfos, String text) {
        List<AccessibilityNodeInfo> data = new ArrayList<>();
        for (AccessibilityNodeInfo datum : accessibilityNodeInfos) {
            if (datum.getText() != null) {
                if (datum.getText().toString().contains(text)) {
                    data.add(datum);
                }
            }
        }
        return data;
    }


    public static List<AccessibilityNodeInfo> getCheckableNode(List<AccessibilityNodeInfo> accessibilityNodeInfos) {
        List<AccessibilityNodeInfo> data = new ArrayList<>();
        for (AccessibilityNodeInfo datum : accessibilityNodeInfos) {
            if (datum.isCheckable()) {
                data.add(datum);
            }
        }
        return data;
    }

    static Object AccessibilityPolicy;

    static String mAccessibilityPolicy_fieldName = null;
    static boolean isSetAccessibilityPolicyProxy = false;

    /**
     *  添加接口代理
     * @param context
     * @param invocationHandler
     * @param isSetting
     * @throws ClassNotFoundException
     */
    public static void setAccessibilityProxy(Context context, InvocationHandler invocationHandler,  boolean isSetting) throws ClassNotFoundException {
        AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);

        String className = "android.view.accessibility.IAccessibilityManager";
        if (isSetting && !isSetAccessibilityPolicyProxy) {
            isSetAccessibilityPolicyProxy = true;
            Object[] mAccessibilityPolicy = getFieldOjbectForType("android.view.accessibility.AccessibilityManager", am, className);
            AccessibilityPolicy = mAccessibilityPolicy[0];
            mAccessibilityPolicy_fieldName = (String) mAccessibilityPolicy[1];
            Object prxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Class.forName(className)}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {


                    if (AccessibilityPolicy != null) {
                        return invocationHandler.invoke(AccessibilityPolicy, method, args);
                    } else {
                        return null;
                    }
                }
            });
            if (mAccessibilityPolicy_fieldName == null) {
                RefInvoke.setFieldOjbect("android.view.accessibility.AccessibilityManager", "mAccessibilityPolicy", am, prxy);
            } else {
                RefInvoke.setFieldOjbect("android.view.accessibility.AccessibilityManager", mAccessibilityPolicy_fieldName, am, prxy);
            }
        }
        if (!isSetting && isSetAccessibilityPolicyProxy) {
            if (mAccessibilityPolicy_fieldName != null) {
                RefInvoke.setFieldOjbect("android.view.accessibility.AccessibilityManager"
                        , mAccessibilityPolicy_fieldName, am, AccessibilityPolicy);
            } else {
                RefInvoke.setFieldOjbect("android.view.accessibility.AccessibilityManager", "mAccessibilityPolicy", am, AccessibilityPolicy);
            }
            isSetAccessibilityPolicyProxy = false;
            AccessibilityPolicy = null;
        }

    }

    /**
     * hook getEnabledAccessibilityServiceList方法  需要配合setAccessibilityProxy一起使用
     * @param proxy
     * @param method
     * @param args
     * @param context
     * @param accessibilityService
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static Object getEnabledAccessibilityServiceList(Object proxy, Method method, Object[] args,Context context,String accessibilityService) throws InvocationTargetException, IllegalAccessException {
        if (method.getName().equals("getEnabledAccessibilityServiceList")) {
            List<AccessibilityServiceInfo> invoke = (List<AccessibilityServiceInfo>) method.invoke(proxy, args);

            Intent intent = new Intent();
            intent.setClassName(context, accessibilityService);
            @SuppressLint({"QueryPermissionsNeeded", "WrongConstant"}) List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentServices(intent, PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
            if (resolveInfos.size() > 0) {

                AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo() {
                    public void appendShortString(StringBuilder sb, String packageName, String className) {
                        sb.append(packageName).append('/');
                        appendShortClassName(sb, packageName, className);
                    }

                    private void appendShortClassName(StringBuilder sb, String packageName,
                                                      String className) {
                        if (className.startsWith(packageName)) {
                            int PN = packageName.length();
                            int CN = className.length();
                            if (CN > PN && className.charAt(PN) == '.') {
                                sb.append(className, PN, CN);
                                return;
                            }
                        }
                        sb.append(className);
                    }

                    public String flattenToShortString() {
                        StringBuilder sb = new StringBuilder(context.getPackageName().length() + accessibilityService.length());
                        appendShortString(sb, context.getPackageName(), accessibilityService);
                        return sb.toString();
                    }

                    @Override
                    public String getId() {
                        return flattenToShortString();
                    }
                };
                accessibilityServiceInfo.flags = FLAG_ENABLE_ACCESSIBILITY_VOLUME;
                invoke.add(accessibilityServiceInfo);
            }
            return invoke;
        }
        return null;
    }


    public static Object[] getFieldOjbectForType(String class_name, Object obj, String typeClassName) {
        try {
            Class obj_class = Class.forName(class_name);
            Field[] declaredFields = obj_class.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (declaredField.getType().getName().equals(typeClassName)) {
                    declaredField.setAccessible(true);
                    Object[] objects = new Object[2];
                    objects[0] = declaredField.get(obj);
                    objects[1] = declaredField.getName();
                    return objects;
                }
            }
        } catch (SecurityException | IllegalArgumentException |
                 IllegalAccessException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * AccessibilityManager是否可用
     * @param context
     * @param enable
     */
    public static void setAccessibilityManagerEnable(Context context, boolean enable) {

        AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            List<Field> instanceFields = HiddenApiBypass.getInstanceFields(AccessibilityManager.class);
            for (Field instanceField : instanceFields) {
                if (instanceField.getName().equals("mIsEnabled")){
                    try {
                        instanceField.setAccessible(true);
                        instanceField.set(am,enable);
                        break;
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }else {
            RefInvoke.setFieldOjbect("android.view.accessibility.AccessibilityManager", "mIsEnabled", am, enable);
        }
    }


    /**
     * 需要先调用setAccessibilityManagerEnable
     * 通知AccessibilityStateChangeListener
     * 假通知
     *
     * @param context
     * @param enable
     */
    public static void notifyAccessibility(Context context, boolean enable) {
        AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);

        ArrayMap<AccessibilityManager.AccessibilityStateChangeListener, Handler> mAccessibilityStateChangeListeners
                = (ArrayMap<AccessibilityManager.AccessibilityStateChangeListener, Handler>) RefInvoke.getFieldOjbect("android.view.accessibility.AccessibilityManager", am, "mAccessibilityStateChangeListeners");

        for (Map.Entry<AccessibilityManager.AccessibilityStateChangeListener, Handler> accessibilityStateChangeListenerHandlerEntry : mAccessibilityStateChangeListeners.entrySet()) {
            accessibilityStateChangeListenerHandlerEntry.getKey().onAccessibilityStateChanged(enable);
        }
    }


}
